﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace NuMetaheuristics.Utility
{
    public abstract class TypeBuilderBase : ITypeBuilder
    {
        public TypeInstanceDictionary InstanceDictionary { get; set; }

        abstract protected void displayTypeConstructor(ConstructorInfo ctor);
        abstract protected void displayTypeProperty(PropertyInfo property);
        abstract protected void displayObjectProperty(object theObj, PropertyInfo property);
        abstract protected void displayObjectField(object theObj, FieldInfo field);

        abstract protected void displayError(string error);

        abstract protected Type chooseType(Type[] types);
        abstract protected object chooseInstance(object[] objects);
        abstract protected ConstructorInfo chooseConstructor(ConstructorInfo[] ctors);

        abstract protected int promptForInt(string descr);
        abstract protected double promptForDouble(string descr);
        abstract protected string promptForString(string descr);
        abstract protected bool promptForBool(string descr);

        abstract protected void modifyObjectProperties(object theObj);

        public TypeBuilderBase()
        {
            InstanceDictionary = new TypeInstanceDictionary();
        }

        public void ExamineType(Type type)
        {
            if (type.IsInterface)
                throw new ArgumentException();

            ConstructorInfo[] constructors = type.GetConstructors();

            foreach (ConstructorInfo ctor in constructors)
            {
                displayTypeConstructor(ctor);
            }

            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                displayTypeProperty(property);
            }
        }

        public object CreateType(Type type)
        {
            if (type.IsInterface || type.IsAbstract)
            {
                List<Type> concreteTypes = findConcreteTypes(type);

                if (concreteTypes.Any())
                {
                    type = chooseType(concreteTypes.ToArray());
                }
                else
                {
                    displayError(String.Format("no concrete types found for type {0}. Aborting...", type));
                }
            }

            if (type == typeof(double))
            {
                return promptForDouble(type.Name);
            }
            else if (type == typeof(int))
            {
                return promptForInt(type.Name);
            }
            else if (type == typeof(bool))
            {
                return promptForBool(type.Name);
            }
            else if (type == typeof(string))
            {
                return promptForString(type.Name);
            }

            //handle arrays
            if (type.IsArray)
            {
                
            }

            List<object> objects;
            if (InstanceDictionary.TryGetObjects(type, out objects))
            {
                if (promptForBool("Use an existing instance of this type?"))
                {
                    return chooseInstance(objects.ToArray());
                }
            }

            ConstructorInfo ctor = chooseConstructor(type.GetConstructors());

            List<object> parameters = new List<object>();
            foreach (ParameterInfo parameter in ctor.GetParameters())
            {
                Type parameterType = parameter.ParameterType;
                object obj = CreateType(parameterType);

                if (obj == null)
                    throw new NullReferenceException();

                parameters.Add(obj);
            }

            object theObj = ctor.Invoke(parameters.ToArray());
            InstanceDictionary.AddObject(theObj);

            return theObj;
        }

        public List<Type> findConcreteTypes(Type type)
        {
            Type discovererType = typeof(Discoverer<>).MakeGenericType(type);
            ConstructorInfo discovererCtor = discovererType.GetConstructor(new Type[] { });
            object discoverer = discovererCtor.Invoke(new object[] { });

            List<Type> types = (List<Type>)discoverer.GetType().GetMethod("DiscoverTypes").Invoke(discoverer, new object[] { });
            return types;
        }

        public void ExamineObject(object theObj)
        {
            Type type = theObj.GetType();
            foreach(FieldInfo field in type.GetFields())
            {
                displayObjectField(theObj, field);
            }

            foreach (PropertyInfo property in type.GetProperties())
            {
                displayObjectProperty(theObj, property);
            }
        }

        public void ModifyObject(object theObj)
        {
            modifyObjectProperties(theObj);
        }
    }
}
